/*
 * Copyright (C) 2008 GeoScheduler Team, as stated on <http://www.geoscheduler.org/authors>.
 * 
 * This file is part of GeoScheduler.
 * 
 * GeoScheduler is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GeoScheduler is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with GeoScheduler. If not, see <http://www.gnu.org/licenses/>.
 */
package org.json.geoscheduler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

import javax.ws.rs.ConsumeMime;
import javax.ws.rs.ProduceMime;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

import org.geoscheduler.rest.utils.MIMEConstants;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

/**
 * Implementation of both {@link MessageBodyReader} and {@link MessageBodyWriter} for
 * JSONArray and JSONObject - json.org implementation (eg. not the jettison one).
 * 
 * Input and output streams are (are expected to be) encoded in UTF-8.
 * 
 * Inspired by Marc Hadley's implementation found on blog - http://weblogs.java.net/blog/mhadley/archive/2008/02/integrating_jer_2.html
 *
 * @author srakyi
 */
@Provider
@ConsumeMime(MIMEConstants.APP_JSON)
@ProduceMime(MIMEConstants.APP_JSON)
public class JSONMessageRW implements MessageBodyReader<Object>, MessageBodyWriter<Object> {

	/**
	 * @see javax.ws.rs.ext.MessageBodyReader#isReadable(java.lang.Class)
	 */
	public boolean isReadable(Class<?> cls) {
		if (JSONObject.class.isAssignableFrom(cls) ||
				JSONArray.class.isAssignableFrom(cls)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * {@link MediaType} and {@link MultivaluedMap} (second and third parameters) aren't considered.
	 * {@link InputStream} is expected to be encoded in UTF-8.
	 * 
	 * @see javax.ws.rs.ext.MessageBodyReader#readFrom(java.lang.Class, javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream)
	 */
	public Object readFrom(Class<Object> arg0, MediaType arg1, MultivaluedMap<String, String> arg2, InputStream in) throws IOException {
		StringBuilder sb = new StringBuilder();
		byte[] buff = new byte[2048];
		for (int n = 0; (n = in.read(buff)) > -1;) {
			sb.append(new String(buff, 0, n, Charset.forName("UTF-8")));
		}
		
		try {
			return new JSONObject(new JSONTokener(sb.toString()));
		} catch (JSONException ex) {
			// well it wasn't JSONObject, let's try JSONArray
			// TODO not very effective, can't we find out type of content sooner?
			try {
				return new JSONArray(new JSONTokener(sb.toString()));
			} catch (JSONException e) {
				throw new IOException("Can't parse given stream - isn't parsable neither as JSONObject nor JSONArray.", e);
			}
		}
	}

	/**
	 * @see javax.ws.rs.ext.MessageBodyWriter#getSize(java.lang.Object)
	 */
	public long getSize(Object arg0) {
		return -1;
	}

	/**
	 * @see javax.ws.rs.ext.MessageBodyWriter#isWriteable(java.lang.Class)
	 */
	public boolean isWriteable(Class<?> arg0) {
		if (JSONObject.class.isAssignableFrom(arg0) ||
				JSONArray.class.isAssignableFrom(arg0)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * {@link MediaType} and {@link MultivaluedMap} (second and third parameters) aren't considered.
	 * Content of {@link OutputStream} is encoded in UTF-8 encoding.
	 * 
	 * @see javax.ws.rs.ext.MessageBodyWriter#writeTo(java.lang.Object, javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.OutputStream)
	 */
	public void writeTo(Object objOrArr, MediaType arg1, MultivaluedMap<String, Object> arg2, OutputStream out) throws IOException {
		if (objOrArr instanceof JSONObject) {
			JSONObject jo = (JSONObject)objOrArr;
			out.write(jo.toString().getBytes(Charset.forName("UTF-8")));
		} else if (objOrArr instanceof JSONArray) {
			JSONArray ja = (JSONArray)objOrArr;
			out.write(ja.toString().getBytes(Charset.forName("UTF-8")));
		} else {
			out.write("Error, unknown object to be written.".getBytes());
		}
	}
}
